/*
 * Author:  Copyright (c) 2013 Toby
 * E-Mail:  smurffit@gmail.com
 * Web:     https://code.google.com/p/chrome-mfi-filter4kiva/
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, see <http://www.gnu.org/licenses/>.
 */

'use strict';

/* ----- FUNC ----- */

/**
 * The result of the cleanup() to be returned to background.js
 * @param count
 *            {number} the amount of filtered items
 * @param highlights
 *            {boolean} whether items have been highlighted
 * @param filtered
 *            {Array} the list of filtered MFIs
 */
function result(count, highlights, filtered)
{
	this.count = count;
	this.highlights = highlights;
	this.filtered = filtered;
}

/* ----- END FUNC ----- */

/* ----- VARS ----- */

/**
 * 0 = off, 1 = verbose, 2 = debug
 * @type {number}
 */
var DEBUG = 0;

/**
 * Runtime configuration
 * @see background.config
 * @type {config}
 */
var config = {
//
};

/**
 * filtered result counter, has to be reset on load
 * @type {number}
 */
var count = 0;

/**
 * whether highlighted results have been found, has to be reset on load
 * @type {boolean}
 */
var highlights = false;

var
/**
 * ratings[MFI name] -> {number} rating
 * @type {Object}
 */
ratings,

/**
 * ratesImg[{number} rating] -> {String} rating as HTML code
 * @type {Object}
 */
ratesImg,

/**
 * global timer for the DOM event listener
 * @type {window.setTimeout}
 */
timer,

/**
 * filtered MFIs on the current page so they can be shown on the pop-up page
 * @type {Array}
 */
filtered;

/* ----- END VARS ----- */

/**
 * Drop the previous state to ensure correct results also stops any remaining
 * timers
 */
function resetPage()
{
	if (timer)
	{
		clearTimeout(timer);
		timer = null;
	}

	count = 0;
	highlights = false;
	// TODO if we can rely on a single cleanup() call then filtered can be local
	filtered = [];
}

/**
 * Starts a timer which will call the cleanup() function after the time elapsed.
 * The timer will be reset every time this function is called. The goal is to
 * perform the expensive work as rarely as possible (ideally only once) while
 * keeping the time the page has to wait for us as low as possible.
 */
function scheduleCleanup()
{
	if (config.block || config.highlight)
	{
		if (timer)
		{
			// reset existing timer
			clearTimeout(timer);
		}

		// TODO this may no longer apply with the webRequest API
		// I tried several different timers.
		// Higher timers reduce the overhead but may delay the progress;
		// lower timers can timeout too fast leading to unnecessary cycles
		// 200 should be the maximum, 40 the minimum. 100 Seems to be a good
		// default
		timer = setTimeout(function()
		{
			cleanup();
		}, 80);
	}
}

// Receive messages from the background.js page
chrome.extension.onMessage.addListener(function(request, sender)
{
	// FIXME clean this mess up
	// reset is requested when a new page is requested to discard the state
	if (request.action == 'reset')
	{
		resetPage();
		if (DEBUG > 1)
		{
			console.log('State: ' + request.value);
		}
		config = JSON.parse(request.value);

		ratings = JSON.parse(request.rates);
		ratesImg = JSON.parse(request.ratesImg);

		if (request.type === 'onCompleted')
		{
			setTimeout(function()
			{
				var ti = setInterval(function()
				{
					if (document.readyState == 'complete')
					{
						scheduleCleanup();
						clearInterval(ti);
					}
				}, 80);
			}, 200);
		}
		else if (request.type === 'direct')
		{
			scheduleCleanup();
		}
		else
		{
			document.getElementById('listingsColStock').addEventListener('DOMNodeInserted',
					scheduleCleanup, false);
		}
	}
	else if (request.action == 'colorHidden')
	{
		if (DEBUG > 1)
		{
			console.log('colorHidden.');
		}
		colorHidden(request.value);
	}
});

function getStyleSheet()
{
	for ( var i = 0, l = document.styleSheets.length; i < l; i++)
	{
		if (document.styleSheets[i].title === 'injected')
		{
			return document.styleSheets[i];
		}
	}
	if (DEBUG > 1)
	{
		console.log('CSS rule not found - creating new one.');
	}

	var cssStyle = document.createElement('style');
	cssStyle.type = 'text/css';
	cssStyle.id = 'injected';
	cssStyle.title = 'injected';

	cssStyle.appendChild(document.createTextNode('.filtered{' + config.colorFilteredHide + ';}'));
	cssStyle.appendChild(document.createTextNode('.highlight{' + config.colorHighlighted + ';}'));

	document.getElementsByTagName('head')[0].appendChild(cssStyle);
	return getStyleSheet();
}

/**
 * Shows / hides and colorizes filtered items
 * @param show
 *            {boolean} whether the items should be shown or hidden
 */
function colorHidden(show)
{
	if (show)
	{
		setCssRule(0, '.filtered{' + config.colorFilteredShow + ';}');
	}
	else
	{
		setCssRule(0, '.filtered{' + config.colorFilteredHide + ';}');
		// showing and hiding several items messes up the scroll position
		// (usually
		// ends up with the page scrolled down to the bottom). Avoid this by
		// scrolling to the top of the list.
		document.getElementById('listingsForm').scrollIntoView();
	}
}

/**
 * Inserts or replaces a CSS rule with the given content
 * @param ruleId
 *            {number} the ruleId
 * @param ruleContent
 *            {String} the CCSS content of the rule
 */
function setCssRule(ruleId, ruleContent)
{
	var cssStyle = getStyleSheet();
	if (ruleId < cssStyle.rules.length)
	{
		cssStyle.deleteRule(ruleId);
	}
	cssStyle.insertRule(ruleContent, ruleId);
}

/**
 * Checks for RegExp issues like an open loan page which is uninteresting a
 * match to /lend*
 * @returns {boolean} whether the pre-check was successful
 */
function precheckCleanup(regExFilter, regExHighlight)
{
	// FIXME on a lender page E2 kicks in on contentchange
	if (DEBUG > 0)
	{
		console.log('precheckCleanup()');
	}

	if (!config.block && !config.highlight)
	{
		console.log('Invalid filter configured.');
		chrome.extension.sendMessage(new result('E1', false, ''));
		return false;
	}
	else if ((!regExFilter || (regExFilter.source.length < 6)) &&
			(!regExHighlight || (regExHighlight.source.length < 6)))
	{
		console.log('Invalid filter configured.');
		chrome.extension.sendMessage(new result('E1', false, ''));
		return false;
	}
	else if (!document.getElementById('listingsColStock'))
	{
		console.log('Couldn\'t find result coloumn.');
		chrome.extension.sendMessage(new result('E2', false, ''));
		return false;
	}
	return true;
}

// The core method of this extension goes through the results and hides unwanted
// entries
function cleanup()
{
	var regExFilter = new RegExp(config.regExFilter);
	var regExHighlight = new RegExp(config.regExHighlight);
	if (!precheckCleanup(regExFilter, regExHighlight))
	{
		if (DEBUG > 0)
		{
			console.log('Precheck failed.');
		}
		return;
	}
	setCssRule(0, '.filtered{' + config.colorFilteredHide + ';}');
	var listingsColStock = document.getElementById('listingsColStock');
	var itemNode, articleNodes = listingsColStock.getElementsByTagName('article');

	for ( var i = 0, l = articleNodes.length; i < l; i++)
	{
		// usually node 7 is correct, for teams it's node 9
		itemNode = articleNodes[i].childNodes[3].childNodes[1].childNodes[7];
		if (itemNode.getAttribute('class') !== 'mfi')
		{
			itemNode = articleNodes[i].childNodes[3].childNodes[1].childNodes[9];
			if (itemNode.getAttribute('class') !== 'mfi')
			{
				console.log('not mfi: ' +
						articleNodes[i].childNodes[3].childNodes[1].childNodes[7].innerHTML);
				continue;
			}
		}

		// node is not filtered, check if it should be
		if ((articleNodes[i].className.indexOf('filtered') === -1) &&
				(articleNodes[i].className.indexOf('highlight') === -1))
		{
			if (regExFilter.test(itemNode.textContent))
			{
				count++;
				articleNodes[i].className += ' filtered';
				filtered.push(itemNode.textContent.substring(12));
			}
			else if (regExHighlight.test(itemNode.textContent))
			{
				highlights = true;
				articleNodes[i].className += ' highlight';
			}
		}

		if (config.showRatingStars && (itemNode.innerHTML.indexOf('img') === -1))
		{
			var stars = ratesImg[ratings[itemNode.textContent.substring(12)]];
			if (!stars && (itemNode.innerHTML.indexOf('Update') === -1))
			{
				itemNode.innerHTML += '<br /><span style="color:red;">Update Field Partners</span>';
			}
			else if (stars)
			{
				itemNode.innerHTML += '<br />' + stars;
			}
		}
		if (DEBUG > 0 && (itemNode.innerHTML.indexOf('undefined') > 0))
		{
			console.log('undefined: ' + itemNode.innerHTML);
		}
	}
	if (DEBUG > 0)
	{
		console.log('Filtered: ' + count + ' / Highlights: ' + highlights);
	}
	// let the background.js know what we found
	chrome.extension.sendMessage(new result(count, highlights, filtered.join('#')));
	listingsColStock.removeEventListener('DOMNodeInserted', scheduleCleanup);
	getStyleSheet();
}
